#! /usr/bin/env python

# $XORP: other/testbed/tools/xtconf.py,v 1.17 2002/03/08 22:57:36 atanu Exp $

from xml.sax import saxutils
from xml.sax import make_parser
from xml.sax.handler import feature_namespaces
import string
import getopt, sys, os

from xtxml import Xtphysical, Xtconfig, Xorp
from xtutils import nw, unique, MyError, Physical
from xthpconfig import HPConfig, hp_control_ipv4, hp_control_mask
from xtvars import HOST, CONF, SWITCH_CONF, KERNEL_DIR
from xtreset import reboot

"""
Configure the xorp testbed
"""

def install(physical, fancy, _reboot, debug):
    """
    Install all the configuration setup for the testbed
    """

    # 1) Check that we are on the correct host.

    global HOST
    import os
    if HOST != os.uname()[1]:
        print >> sys.stderr, "Not on config host: " + HOST
        sys.exit(1)

    # 2) Generate the <testbed_config> file. The copy it into place if it is
    #    different.

    import tempfile
    tmp = tempfile.mktemp("xt")
    if 1 == debug:
        print "config.xml temporary file name: " + tmp

    fp = open(tmp, "w")

    save = sys.stdout
    sys.stdout = fp

    print '<!-- Generated by xtconf.py -->'
    generate(physical, fancy, debug)

    sys.stdout = save
    
    fp.close()

    #
    # Before we go any further lets run another verification pass.
    #
    try:
        Verify(physical, tmp, debug).start()
    except MyError, error:
        print >> sys.stderr, str(error)
        os.remove(tmp)
        sys.exit(1)

    # Is a config file already installed? If not just copy this one into
    # place.

    global CONF
    copy_if_different("configuration", tmp, CONF)
    os.remove(tmp)

    # 3) Generate HP Switch config and copy it info place if it has changed.

    tmp = tempfile.mktemp("conf")
    if 1 == debug:
        print "switch.conf file name: " + tmp

    fp = open(tmp, "w")

    save = sys.stdout
    sys.stdout = fp
    HPConfig(CONF, os.path.abspath(fancy), debug).start()
    sys.stdout = save
    fp.close()

    changed = []

    global SWITCH_CONF
    if 1 == copy_if_different("HP switch config", tmp, SWITCH_CONF):
        changed = ["xorpsw1"]
    os.remove(tmp)

    # 4) Copy the kernels into place if they have changed.
    # Get the list of kernels.

    list, kernels, noconfig, router = Xtconfig(CONF, debug).start()
    for i in kernels:
        global KERNEL_DIR
        if 1 == copy_if_different("kernel", i[0], KERNEL_DIR + i[1]):
            changed += [i[1]]

    #
    # Reboot the suckers
    #
    if not _reboot in (0, 1, 2):
        print >> sys.stderr, "Illegal value for _reboot " + repr(_reboot)
        return

    if 0 == _reboot:
        return

    if 2 == _reboot:
        changed = [i[1] for i in kernels] + ["xorpsw1"]

    for i in changed:
        print "Rebooting " + i
        s, r = reboot(i)
        if 0 == s:
            print >> sys.stderr, r
        else:
            print r

def copy_if_different(description, source, destination):
    """
    Copy source to destination if the files differ.
    """

    import os, shutil

    changed = 0

    # Check source
    if not os.access(source, os.F_OK):
        print description.capitalize() + " file " + source + " not available"
        return 0
    
    print "Generated " + description + " [" + destination + "] ",

    if os.access(destination, os.F_OK):
        import filecmp
        if filecmp.cmp(source, destination):
            print "identical unchanged" 
        else:
            print "differs overwriting "
            if not os.access(destination, os.W_OK):
                try:
                    os.remove(destination)
                except:
                    print  "ERROR cannot overwrite or remove"
                    return 0
            shutil.copyfile(source, destination)
            changed = 1
    else:
        if os.access(os.path.dirname(destination), os.W_OK):
            print "new installing"
            shutil.copyfile(source, destination)
            changed = 1
        else:
            print "ERROR cannot write"
            return 0

    return changed
    
def generate(physical, fancy, debug):
    """
    Generate a <testbed_config> file from the fancy file and the
    <testbed_physical>
    """

    #
    # Send all the output to a string by redirecting stdout
    #
    import StringIO
    sio = StringIO.StringIO()
    save = sys.stdout
    sys.stdout = sio

    #
    #
    #
    print "<script>"
    f=open(fancy)
    print f.read()
    f.close()
    print "</script>"


    #
    # Generate the control and main net part of the config
    #
    Defaults(physical, debug).start()

    #
    # 
    #
    from xtparse import process
    try:
        process(debug, physical, fancy)
    except MyError, error:
        sys.stderr.write("%s\n" % str(error))
        sys.exit(1)

    #
    # Restore stdout
    #
    sys.stdout = save

    print "<testbed_config>"
    print sio.getvalue(),
    print "</testbed_config>"
    

class Defaults(saxutils.DefaultHandler):
    """
    Create a <testbed_config> file from the <testbed_physical> file that
    contains the settings for the main net and the control net.
    """

    vlans = {}

    def __init__(self, fname, debug = 0):
	self.fname = fname
	self.debug = debug
	
    def characters(self, ch):
        self.ch += ch

    def start(self):
	self.vlans["control"] = ()
	self.vlans["main"] = ()

	parser = make_parser()
	parser.setFeature(feature_namespaces, 0)
	parser.setContentHandler(self)
	parser.parse(self.fname)

#	global hp_control_ipv4, hp_control_mask
#	print '<vlan name="CONTROL" ipv4="%s" mask="%s">' % \
#	      (hp_control_ipv4, hp_control_mask)

	print '<vlan name="CONTROL">'
        for i in self.vlans["control"]:
	    print '\t<host name="' + i[0] + '">'
	    print '\t\t<ipv4 mac="' + i[2] + '" port="' + i[1] + '">'
	    print '\t\t</ipv4>'
	    print '\t</host>'
	print '</vlan>'

	print '<vlan name="MAIN">'
	for i in self.vlans["main"]:
	    print '\t<host name="' + i[0] + '">'
	    print '\t\t<ipv4 mac="' + i[2] + '" port="' + i[1] + '">'
	    print '\t\t</ipv4>'
	    print '\t</host>'
	print '</vlan>'


    def startElement(self, name, attrs):
	self.ch = ""
	self.interface = ""
        
	if "host" == name:
	    self.host = nw(attrs.getValue('name'))

        if "mac" == name:
	    self.interface = nw(attrs.getValue('name'))
	    self.port = nw(attrs.getValue('port'))
		
    def endElement(self, name):
	if("mac" == name):
	    if "main" == self.interface or "control" == self.interface:
		if 1 == self.debug:
		    print self.host, self.interface, self.port, nw(self.ch)

		self.vlans[self.interface] = self.vlans[self.interface] +\
					     ((self.host, \
					      self.port, \
					      nw(self.ch)),)

        self.ch = ""

class Verify(saxutils.DefaultHandler):
    """
    Verify that a <testbed_config> file is consistent with a
    <testbed_physical>.
    """

    inconfig = 0
    invlan = 0
    inhost = 0
    
    # We need to verify that a bunch of entities are unique so construct
    # lists and then check for duplicates
    vlans = ()
    network = ()
    ports = ()
    addresses = ()

    def __init__(self, physical, config, debug = 0):
	self.physical = physical
	self.config = config
	self.debug = debug
	
    def characters(self, ch):
        self.ch += ch

    def start(self):
        # Before we start read in the <testbed_physical> file

	self.x = Xorp(self.physical, self.debug).start()

	parser = make_parser()
	parser.setFeature(feature_namespaces, 0)
	parser.setContentHandler(self)
	parser.parse(self.config)

        # Now do the uniqueness test
        unique("vlan", self.vlans)
        unique("network", self.network)
        unique("ports", self.ports)
        unique("ipv4 address", self.addresses)

    def startElement(self, name, attrs):
	self.ch = ""

        if "testbed_config" == name:
            self.inconfig = 1

        if self.inconfig != 1:
            return

        if "vlan" == name:
            self.invlan = 1
            self.vlan_name = nw(attrs.getValue('name'))
            try:
                self.ipv4 = nw(attrs.getValue('ipv4'))
                self.mask = nw(attrs.getValue('mask'))
            except:
                self.ipv4 = ""
                self.mask = ""
		
        if self.invlan != 1:
            return

        if "host" == name:
            self.inhost = 1
            self.host_name = nw(attrs.getValue('name'))

        if self.inhost != 1:
            return

        if "ipv4" == name:
            self.mac = nw(attrs.getValue('mac'))
            self.port = nw(attrs.getValue('port'))
            try:
                self.interface = nw(attrs.getValue('name'))
            except:
                self.interface = ""
        
    def endElement(self, name):
        if "testbed_config" == name:
            self.inconfig = 0

        if "vlan" == name:
            self.invlan = 0
            self.vlans = self.vlans + (self.vlan_name,)
            self.network = self.network + (self.ipv4,)
            if 1 == self.debug:
                print self.vlans
                print self.network

        if "host" == name:
            self.inhost = 0

        if "ipv4" == name:
            self.ports = self.ports + (self.port,)
            address = nw(self.ch)
            self.addresses = self.addresses + (address,)
            if 1 == self.debug:
                print self.ports
                print self.addresses

            self.verify(self.x, self.host_name, self.mac, self.port, \
                        self.interface)

            if "" != address:
                self.netcheck(address, self.ipv4, self.mask)

        self.ch = ""

    def verify(self, x, host_name, mac, port, interface):
        """
        Check the entries in the configuration file against the physical
        config
        """

        def compare(error, port, physical, config):
            if physical != config:
                print "%s mismatch on port %s: physical %s config %s" % \
                      (error, port, physical, config)

        # Use the port on the switch as the key
        found = 0
        for i in x:
            if port == i[2]:
                found = 1
                compare("Hostname", port, i[0], host_name)
                compare("Mac", port, i[1], mac)
                if "" != interface:
                    compare("Interface", port, i[6], interface)
                    
                if 1 == self.debug:
                    print host_name, mac, port, interface
                    print i

        if 0 == found:
            print "port %s is not connected to the switch" % port
            
    def netcheck(self, address, network, mask):
        """
        Verify that that address is actually in the same network
        """

        def aton(a):
            import socket
            import struct

            return struct.unpack("I", socket.inet_aton(a))[0]

        a = aton(address)
        n = aton(network)
        m = aton(mask)

        if ((a & m) == m) and ((n & m) == m):
            print "%s not in same network %s" % (address, network)
            print (a & m), (n & m)

#
# Attempt to verify all the mac addresses in the configuration file against
# the machines's and port location. This command runs a "ssh ifconfig" to
# all the hosts. So they need to be running freebsd and accessible.
#
def ifcheck(physical, debug):
    "Check <testbed_physical> against the ifconfig output"
    # First read in the <testbed_physical> file

    ports = ()
    macs = ()
    cable = ()
    for i in Xtphysical(physical, debug).start():
        if 1 == debug:
            print i
        # If there is valid mac address then check it
        if -1 != string.find(i["mac"], ":"):
            from xtifset import verify
            status, msg = verify(i["host"], i["vif"], i["mac"], i["port"])
            if 1 != status:
                print msg
            ports = ports + (i["port"],)
            macs = macs + (i["mac"],)
            cable = cable + (i["cable"],)
    unique("ports", ports)
    unique("mac", macs)
    unique("cable", cable)
    
us=\
"""usage: %s [-h|--help] [-d|--debug]
[-D|--default] [-g|--generate] [-s|--switch] [-v|--verify] [-i|--ifcheck]
[-r|--reboot] [-R|--REBOOT] [-I|--install] f|--fancy config2.xt
-p|--physical xorp.xml -c|--config config.xml"""

def main():

    def usage():
        global us
	print us % sys.argv[0]
    try:
	opts, args = getopt.getopt(sys.argv[1:], "hsDrRIgvip:f:c:d", \
				   ["help", "switch", "default", \
                                   "reboot", \
                                   "REBOOT", \
                                   "install", \
                                    "generate", \
                                    "verify", \
                                    "ifcheck", \
                                    "physical=", "fancy=", "config=", \
                                    "debug"])
    except getopt.GetoptError:
	usage()
	sys.exit(1)

    default = 0
    reboot = 0
    inst = 0
    gen = 0
    switch = 0
    verify = 0
    ifcheckp = 0
    physical = ""
    fancy = ""
    config = ""
    debug = 0
    for o, a in opts:
	if o in ("-h", "--help"):
	    usage()
	    sys.exit()
	if o in ("-D", "--default"):
	    default = 1
	if o in ("-r", "--reboot"):
	    reboot = 1
	if o in ("-R", "--REBOOT"):
	    reboot = 2
	if o in ("-I", "--install"):
	    inst = 1
	if o in ("-g", "--generate"):
	    gen = 1
        if o in ("-s", "--switch"):
            switch = 1
        if o in ("-v", "--verify"):
            verify = 1
        if o in ("-i", "--ifcheck"):
            ifcheckp = 1
	if o in ("-p", "--physical"):
	    physical = a
	if o in ("-f", "--fancy"):
	    fancy = a
	if o in ("-c", "--config"):
	    config = a
	if o in ("-d", "--debug"):
	    debug = 1

    p = 0
    if "" == physical:
        p = Physical(debug)
        physical = p.get()

    if 1==default:
        if "" != physical:
            Defaults(physical, debug).start()
        else:
            usage()

    if 1==inst:
        if "" != physical and "" != fancy:
            install(physical, fancy, reboot, debug)
        else:
            usage()

    if 1==gen:
        if "" != physical and "" != fancy:
            generate(physical, fancy, debug)
        else:
            usage()

    if 1==switch:
        if "" != config:
            HPConfig(config, os.path.abspath(config), debug).start()
        else:
            usage()

    if 1==verify:
        if "" != physical and "" != config:
            Verify(physical, config, debug).start()
        else:
            usage()

    if 1==ifcheckp:
        if "" != physical:
            ifcheck(physical, debug)
        else:
            usage()

    if p:
        p.remove()

    sys.exit(0)

if __name__ == '__main__':
    main()
